package middleware

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"proxy_resource_test/services/dingtalk"
	"proxy_resource_test/utils/resp"
	"sync"
	"time"
)

// 限流配置（可改为配置中心动态加载）
var (
	reqThreshold  = 3                                    // 时间窗口内允许的最大请求数
	windowSize    = 1 * time.Second                      // 统计时间窗口
	penaltyLevels = []int{5, 10, 30, 90, 300, 600, 1000} // 封禁时间阶梯（秒）
	resetDuration = 24 * time.Hour                       // 无活动后重置记录的时间
	shardCount    = 256                                  // hash分片数量，增加命中率，减少锁持有竞态条件
)

// 用户限流记录
type limitRecord struct {
	Count        int       // 当前窗口请求计数
	PenaltyIndex int       // 当前封禁阶梯索引
	WindowStart  time.Time // 时间窗口开始时间
	BlockUntil   time.Time // 封禁结束时间
	LastActive   time.Time // 最后活跃时间
}

// 分片结构
type limiterShard struct {
	sync.RWMutex
	records map[string]*limitRecord
}

var shards = make([]*limiterShard, shardCount)

func init() {
	for i := 0; i < shardCount; i++ {
		shards[i] = &limiterShard{records: make(map[string]*limitRecord)}
	}
}

// 获取分片索引
//
//	0x7FFFFFFF 代表 32 位有符号整数的最大值。
//
// 0x: 表示十六进制数。
// 7FFFFFFF: 十六进制数，转换为十进制为 2147483647。
// 32 位有符号整数的范围：
// 最小值：-2147483648
// 最大值：2147483647
// hash << 5 将 hash 左移 5 位，相当于乘以 32。
func getShardIndex(key string) int {
	hash := 0
	for _, c := range key {
		hash = (hash << 5) - hash + int(c)
	}
	return (hash & 0x7FFFFFFF) % shardCount
}

// 生成限流键（IP/UserID + Path）
func getLimitKey(c *gin.Context) string {
	// 如果是已登录用户，改用 UserID（需要根据项目身份验证逻辑调整）
	clientIP := c.ClientIP()
	deviceSn := c.GetHeader("_sn")
	if len(deviceSn) < 1 {
		deviceSn = "web"
	}
	path := c.Request.RequestURI // 使用路由定义的路径（非动态路径）
	return fmt.Sprintf("%s:%s:%s", clientIP, path, deviceSn)
}

// RateLimiter  梯度限流中间件
func RateLimiter() gin.HandlerFunc {
	return func(c *gin.Context) {
		key := getLimitKey(c)
		shardIndex := getShardIndex(key)
		shard := shards[shardIndex]

		now := time.Now()

		// 锁粒度仅限于操作限流记录
		shard.Lock()
		// 惰性清理：如果记录长时间未活跃，则删除
		if record, exists := shard.records[key]; exists {
			if now.Sub(record.LastActive) > resetDuration {
				delete(shard.records, key)
			}
		}

		// 获取或初始化限流记录
		record, exists := shard.records[key]
		if !exists {
			record = &limitRecord{
				WindowStart: now,
				LastActive:  now,
			}
			shard.records[key] = record
		}

		// 如果在封禁期内，直接拒绝请求
		if now.Before(record.BlockUntil) {
			shard.Unlock()
			//end: record.BlockUntil.Format(time.DateTime)
			resp.Json(c, resp.ServerBusy, fmt.Sprintf("Frequent requests. Try again later"), nil)
			c.Abort()
			return
		}
		//a := now.Sub(record.WindowStart)
		//fmt.Println(a)
		// 如果超过时间窗口，重置计数器
		if now.Sub(record.WindowStart) > windowSize {
			record.WindowStart = now
			record.Count = 0
		}

		record.Count++
		record.LastActive = now

		// 超过阈值则触发封禁
		if record.Count > reqThreshold {
			index := record.PenaltyIndex
			if index >= len(penaltyLevels) {
				index = len(penaltyLevels) - 1
			}
			banDuration := time.Duration(penaltyLevels[index]) * time.Second
			record.BlockUntil = now.Add(banDuration)
			record.PenaltyIndex++ // 升级处罚级别
			record.WindowStart = now
			record.Count = 0

			// 在处罚等级为 3 或 5 时发送告警（异步调用）
			if index == 3 || index == 5 {
				go sendAlertMessage(key, index, banDuration)
			}
			shard.Unlock()
			msg := fmt.Sprintf("Requests are too frequent, please wait for %s and try again", banDuration.String())
			resp.Json(c, resp.ServerBusy, msg, nil)
			c.Abort()
			return
		}
		shard.Unlock()

		c.Next()
	}
}

func sendAlertMessage(key string, index int, banDuration time.Duration) {
	go dingtalk.Send(fmt.Sprintf(" 路由: %s, 封禁等级: %d, 封禁时间: %s", key, index, banDuration))
}
